﻿using System;

namespace PhenomMsrTweaker
{
	/// <summary>
	/// Manages various Cool & Quiet related settings of AMD K10 CPUs.
	/// </summary>
	public static class K10Manager
	{
		#region Core-specific functions.

		/// <summary>
		/// Returns the current hardware P-state of a core.
		/// </summary>
		public static int GetCurrentPState(int coreIndex)
		{
			return (int)((Program.Ols.ReadMsr(0xC0010071u, coreIndex) >> 16) & 0x7);
		}

		/// <summary>
		/// Puts a core into the specified software P-state.
		/// </summary>
		public static void SwitchToPState(int pStateIndex, int coreIndex)
		{
			if (pStateIndex < 0 || pStateIndex > 4)
				throw new ArgumentOutOfRangeException("pStateIndex");

			Program.Ols.WriteMsr(0xC0010062u, (ulong)pStateIndex, coreIndex);
		}

		#endregion

		#region Functions affecting all cores.

		/// <summary>
		/// Returns the highest allowed hardware P-state index.
		/// </summary>
		public static int GetHighestPState()
		{
			// value of interest: F3xDC[PstateMaxVal]
			uint settings = Program.Ols.ReadPciConfig(0xC3, 0xDC);
			return (int)((settings >> 8) & 0x7);
		}

		/// <summary>
		/// Sets the highest allowed hardware P-state index.
		/// </summary>
		public static void SetHighestPState(int maxIndex)
		{
			if (maxIndex < 0 || maxIndex > 4)
				throw new ArgumentOutOfRangeException("maxIndex");

			uint reference = Program.Ols.ReadPciConfig(0xC3, 0xDC);
			uint newSettings = (reference & 0xFFFFF8FFu) | ((uint)maxIndex << 8);

			Program.Ols.WritePciConfig(0xC3, 0xDC, newSettings);
		}


		/// <summary>
		/// Returns the maximum supported core multiplier.
		/// </summary>
		public static double GetMaxMulti()
		{
			ulong msr = Program.Ols.ReadMsr(0xC0010071u);
			int maxDoubleMulti = (int)(msr >> 49) & 0x3F;

			return (maxDoubleMulti == 0 ? 31.5 : 0.5 * maxDoubleMulti);
		}

		/// <summary>
		/// Gets the CPU's VID limits (for both CPU VID and NB VID).
		/// </summary>
		public static void GetVidLimits(out double min, out double max)
		{
			ulong msr = Program.Ols.ReadMsr(0xC0010071u);

			uint minValue = (uint)(msr >> 42) & 0x7F;
			uint maxValue = (uint)(msr >> 35) & 0x7F;

			min = (minValue == 0 ? 0.0125 : 1.55 - minValue * 0.0125);
			max = (maxValue == 0 ? 1.55   : 1.55 - maxValue * 0.0125);
		}

        /// <summary>
        /// Returns the family in hex.
        /// </summary>
        public static int GetFamily()
        {
            uint eax = 0, ebx = 0, ecx = 0, edx = 0;
            int baseFamily, extFamily, family;
            if (Program.Ols.Cpuid(0x80000001u, ref eax, ref ebx, ref ecx, ref edx) == 0)
                throw new NotSupportedException("Cpuid()");

            baseFamily = (int)((eax >> 8) & 0xF);
            extFamily = (int)((eax >> 20) & 0xF);
            family = baseFamily + extFamily;
            int tmp1 = family % 16;
            int tmp10 = (int)(family / 16);
            family = tmp10 * 10 + tmp1;
            return family;
        }

		/// <summary>
		/// Returns the number of (enabled) CPU cores.
		/// </summary>
		public static int GetNumCores()
		{
			uint eax = 0, ebx = 0, ecx = 0, edx = 0;

			if (Program.Ols.Cpuid(0x80000008u, ref eax, ref ebx, ref ecx, ref edx) == 0)
				throw new NotSupportedException("Cpuid()");

			return (int)(ecx & 0xFF) + 1;
		}

		#endregion

		#region Turbo.

		private static int _isTurboSupported = -1;

		/// <summary>Returns true if the CPU supports Turbo.</summary>
		public static bool IsTurboSupported()
		{
			// cached
			if (_isTurboSupported == -1)
			{
				uint eax = 0, ebx = 0, ecx = 0, edx = 0;
				if (Program.Ols.Cpuid(0x80000007u, ref eax, ref ebx, ref ecx, ref edx) != 0)
					_isTurboSupported = ((int)edx >> 9) & 1;
				else
					_isTurboSupported = 0;
			}

			return (_isTurboSupported != 0);
		}

		/// <summary>
		/// Returns true if the Turbo is not supported or locked
		/// (number of boosted states and Turbo cores).
		/// </summary>
		public static bool IsTurboLocked()
		{
			if (!IsTurboSupported())
				return true;

			uint lower = Program.Ols.ReadPciConfig(0xC4, 0x15C);
			return ((lower & 0x80000000u) != 0);
		}

		/// <summary>
		/// Tries to enable/disable the Turbo and returns true if successful.
		/// If the number of boosted P-states is unlocked, it is set appropriately.
		/// </summary>
		public static bool SetTurbo(bool enable)
		{
			if (!IsTurboSupported())
				return false;

			uint lower = Program.Ols.ReadPciConfig(0xC4, 0x15C);
			bool isLocked = ((lower & 0x80000000u) != 0);

			uint newLower = (lower & 0xFFFFFFFCu) | (enable ? 3u : 0u);
			// set the number of boosted states if unlocked
			if (!isLocked)
				newLower = (newLower & 0xFFFFFFFBu) | (enable ? 1u << 2 : 0u);

			if (newLower != lower)
				Program.Ols.WritePciConfig(0xC4, 0x15C, newLower);

			return true;
		}

		/// <summary>
		/// Tries to set the maximum number of cores in the Turbo state at a time and
		/// returns true if successful.
		/// </summary>
		public static bool SetNumTurboCores(int num)
		{
			if (IsTurboLocked())
				return false;

			uint numIdleCores = (uint)(GetNumCores() - num);

			uint lower = Program.Ols.ReadPciConfig(0xC4, 0x16C);
			uint newLower = (lower & 0xFFFFF1FFu) | ((numIdleCores & 7) << 9);

			if (newLower != lower)
				Program.Ols.WritePciConfig(0xC4, 0x16C, newLower);

			return true;
		}

		/// <summary>Returns true if the Turbo is enabled and if there are boosted P-states.</summary>
		public static bool IsTurboEnabled()
		{
			if (!IsTurboSupported())
				return false;

			uint lower = Program.Ols.ReadPciConfig(0xC4, 0x15C);

			return ((lower & 7) == 7); // check if enabled and if there is a boosted state
		}

		/// <summary>Returns the number of boosted (Turbo) P-states.</summary>
		public static int GetNumBoostedStates()
		{
			if (!IsTurboSupported())
				return 0;

			uint lower = Program.Ols.ReadPciConfig(0xC4, 0x15C);

			return ((int)lower >> 2) & 1;
		}

		/// <summary>
		/// Returns the maximum number of cores in the Turbo state at a time.
		/// </summary>
		public static int GetNumTurboCores()
		{
			if (!IsTurboSupported())
				return 0;

			uint lower = Program.Ols.ReadPciConfig(0xC4, 0x16C);
			uint numIdleCores = (lower >> 9) & 7;

			return GetNumCores() - (int)numIdleCores;
		}

		#endregion
	}
}
